﻿using Microsoft.Web.WebView2.Core;
using System.Drawing;
using System.Net;
using System.Text.Json;
using System.Text;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Tnelab.Common;
namespace Tnelab.WebApp
{
    public class EdgeWebBrowser : IWebBrowser,IDisposable
    {
        object _paintLock=new object();
        bool _isPainting = false;
        int _hasPaintCount = 0;
        int _hostWindowPadding = 0;
        Task? _paintTask=null;
        public IWebWindow? HostWindow { get; protected set; }
        protected bool IsCloseed=false;
        protected CoreWebView2Environment? Webview2Env;
        protected CoreWebView2Controller? Webview2Controller;
        protected Dictionary<string, Func<string, Stream>> _schemeHandlerDic { get; } = new Dictionary<string, Func<string, Stream>>();
        protected Dictionary<string, Func<string,Stream>> SchemeHandlerDic { get; } = new Dictionary<string, Func<string, Stream>>();   
        protected Action<Action> UIInvoke { get; private set; }
        public event EventHandler<TitleChangedArgs>? TitleChanged;
        public event EventHandler<PaintEventArg>? Paint;
        public event EventHandler<JsCallEventArgs>? JsCall;
        public event EventHandler<EventArgs>? WebViewIsReady;
        public event EventHandler<EventArgs>? DOMContentLoaded;

        public WebBrowserType BrowserType { get=>WebBrowserType.Edge;  }
        protected virtual void OnTitleChanged(object sender,TitleChangedArgs e)
        {
            this.TitleChanged?.Invoke(sender, e);
        }

        protected virtual void OnPaint(object sender, PaintEventArg arg)
        {
            this.Paint?.Invoke(sender, arg);
        }
        protected virtual void OnJsCall(object sender, JsCallEventArgs arg)
        {
            JsCall?.Invoke(sender, arg);
        }
        protected virtual void OnWebViewReady(object sender,EventArgs args)
        {
            WebViewIsReady?.Invoke(sender,args);
        }
        protected void OnDOMContentLoaded(object sender, EventArgs e)
        {
            if (this.Paint != null)
            {
                _hasPaintCount++;
            }
            this.DOMContentLoaded?.Invoke(sender, e);
        }
        public async Task<string> CallJsAsync(string script)
        {
            if (Webview2Controller == null)
                throw new Exception("Webview2Controller is null.");
            return await Webview2Controller.CoreWebView2.ExecuteScriptAsync(script);
        }
        public void LoadUrl(string url)
        {
            Webview2Controller?.CoreWebView2.Navigate(url);
        }
        public async void OnHostWindowCreated(IWebWindow parent)
        {
            this.HostWindow= parent;
            this.UIInvoke = this.HostWindow.UIInvoke;
            Webview2Env = await CoreWebView2Environment.CreateAsync();
            Webview2Controller = await Webview2Env.CreateCoreWebView2ControllerAsync(this.HostWindow.Handle);
            Webview2Controller.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged;
            Webview2Controller.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived; ;
            Webview2Controller.CoreWebView2.WebResourceRequested += CoreWebView2_WebResourceRequested;
            Webview2Controller.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
            foreach (var sh in _schemeHandlerDic)
            {
                AddSchemeHandlerTo(sh.Key, sh.Value);
            }
            _schemeHandlerDic.Clear();
            Webview2Controller.DefaultBackgroundColor = Color.Transparent;
            Win32Api.GetClientRect(this.HostWindow.Handle, out var hwndRect);
            Webview2Controller.Bounds = new Rectangle(hwndRect.left + _hostWindowPadding, hwndRect.top + _hostWindowPadding, hwndRect.right - _hostWindowPadding * 2, hwndRect.bottom - _hostWindowPadding * 2);
            Webview2Controller.IsVisible = true;
            //Win32Api.SetLayeredWindowAttributes(this.HostWindow.Handle, new Win32Api.COLORREF(255, 0, 0), 0, Win32Api.LWA_COLORKEY);
            //_ = Task.Run(() => {
            //    while (true)
            //    {
            //        if (_hasPaintCount > 0)
            //        {
            //            var stream = new MemoryStream();
            //            this.UIInvoke(() =>
            //            {
            //                _ = Webview2Controller.CoreWebView2.CapturePreviewAsync(CoreWebView2CapturePreviewImageFormat.Png, stream).ContinueWith((task) =>
            //                {
            //                    using (stream)
            //                    {
            //                        this.OnPaint(new PaintEventArg(GetBitmap(stream)));
            //                    }
            //                });
            //            });
            //            _hasPaintCount--;
            //        }
            //        Task.Yield();
            //    }
            //});
            this.OnWebViewReady(this,new EventArgs());
        }

        private void CoreWebView2_DocumentTitleChanged(object sender, object e)
        {
            this.OnTitleChanged(sender, new TitleChangedArgs(this.Webview2Controller.CoreWebView2.DocumentTitle));
        }

        private void CoreWebView2_DOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e)
        {
            this.OnDOMContentLoaded(sender, new EventArgs());
        }
        private void CoreWebView2_WebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
        {
            if (e.Request.Uri.ToLower().StartsWith("use://webapp"))
            {
                e.Request.Uri = $"use://script/webapp.js?Name={this.HostWindow.Name}&WebViewPath={this.GetType().Assembly.GetName().Name}";
            }
            else if (e.Request.Uri.ToLower().StartsWith("service://"))
            {
                var pageUri = new Uri(this.HostWindow.Url);
                e.Request.Uri = $"{e.Request.Uri}?Pre={pageUri.Scheme}://{pageUri.Host}:{pageUri.Port}";
            }
            var uri = new Uri(e.Request.Uri);
            var scheme = uri.Scheme.ToLower();
            var path = e.Request.Uri.Remove(0,scheme.Length+3);
            if (!SchemeHandlerDic.ContainsKey(scheme))
            {
                return;
            }
            using var stream = SchemeHandlerDic[scheme]($"{scheme}://{path}");
            var buffer=new byte[stream.Length];
            stream.Read(buffer, 0, buffer.Length);
            e.Response = Webview2Controller?.CoreWebView2.Environment.CreateWebResourceResponse(new MemoryStream(), (int)HttpStatusCode.OK, HttpStatusCode.OK.ToString(), "Content-Type: text/html; charset = utf-8");
            e.Response.Content.Write(buffer, 0, buffer.Length);
        }
        private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
        {
            var json = e.WebMessageAsJson;
            var jsonDoc = JsonDocument.Parse(json);
            var msgId = jsonDoc.RootElement.GetProperty("QueryId").GetInt32();
            var msg = jsonDoc.RootElement.GetProperty("Message").ToString();
            var random=new Random(DateTime.Now.Millisecond);
            var args = new JsCallEventArgs(random.Next(0,1000000),msgId, msg);
            this.OnJsCall(sender,args);
            Task.Run(() => {
                while (args.Result == null)
                {
                    Task.Yield();
                }
                var result = new { QueryId = msgId, Message = JsonSerializer.Serialize(args.Result)};
                this.UIInvoke(() =>
                {
                    if (!IsCloseed)
                    {
                        Webview2Controller?.CoreWebView2.PostWebMessageAsJson(JsonSerializer.Serialize(result));
                    }
                });

            }, new CancellationTokenSource(10 * 1000).Token);
        }

        public int? OnHostWindowMessageProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
        {
            switch (message)
            {
                case Win32Api.WM_SIZE:
                    if (Webview2Controller != null)
                    {
                        //var cx = Win32Api.LOWORD(lParam);
                        //var cy = Win32Api.HIWORD(lParam);
                        Win32Api.GetClientRect(this.HostWindow.Handle, out var hwndRect);
                        Webview2Controller.SetBoundsAndZoomFactor(new Rectangle(hwndRect.left + _hostWindowPadding, hwndRect.top + _hostWindowPadding, hwndRect.right - _hostWindowPadding * 2, hwndRect.bottom - _hostWindowPadding * 2),1);
                        //_hasPaintCount++;
                    }
                    break;
            }
            return null;
        }
        private Bitmap GetBitmap(Stream stream)
        {
            stream.Position = 0;
            var bitmap= Bitmap.FromStream(stream) as Bitmap;
            return bitmap;
        }
        private void AddSchemeHandlerTo(string scheme, Func<string, Stream> handler)
        {
            Webview2Controller?.CoreWebView2.AddWebResourceRequestedFilter($"{scheme}://*", CoreWebView2WebResourceContext.All);
            SchemeHandlerDic.Add(scheme.ToLower(), handler);
        }
        public void AddSchemeHandler(string scheme, Func<string, Stream> handler)
        {
            if(Webview2Controller != null)
            {
                AddSchemeHandlerTo(scheme, handler);
            }
            else
            {
                _schemeHandlerDic.Add(scheme, handler);
            }
        }

        public void Dispose()
        {
            Webview2Controller?.Close();
            this.IsCloseed = true;
        }
    }
}